<script setup lang="ts">
/**
 * 自定义富文本编辑组件，封装了编辑器，工具栏，图片上传等功能。使用了 @wangeditor/editor-for-vue:5.1.12版本。
 *  注意：图片和视频上传和下载需要自己实现。
 *  @Author 游王子og
 *  @Date 2025/02/13
 *  @Version 1.0.0
 *
 *  prop:
 *    valueHtml: string = '': 内容，默认空字符串
 *    size: number = 2000: 内容长度
 *    enableImg: boolean = false: 是否启用图片上传
 *    enableVideo: boolean = false: 是否启用视频上传
 *  emit:
 *    save: (htmlValue: string) => void: 保存事件，返回内容。该事件在内容发生改变时触发，返回内容。
 *  expose:
 *    clear(): 清空富文本内部资源（图片、视频）
 */
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useCookies } from 'vue3-cookies'
import { deleteFileApi, downloadFileUrl, uploadFileApi } from '@/assets/api/systemManagement/data/file'

const props = withDefaults(defineProps<{
  valueHtml?: string // 内容
  size?: number // 内容长度
  enableImg?: boolean // 是否启用图片上传
  enableVideo?: boolean // 是否启用视频上传
}>(), {
  valueHtml: '',
  size: 2000,
  enableImg: false,
  enableVideo: false
})

const emit = defineEmits<{
  save: [valueHtml: string]
}>()

// 编辑器实例，必须用 shallowRef
const editorRef = shallowRef()

// 内容 HTML
const valueHtml = ref(props.valueHtml)

// 工具栏配置 默认屏蔽工具栏上的图片、视频按钮
const toolbarConfig = ref({
  excludeKeys: ['group-image', 'insertVideo']
})

// 向服务器插入图片集合
const insertImage = ref<string[]>([])
// 向服务器插入视频集合
const insertVideo = ref<string[]>([])
// 编辑器配置
const editorConfig = {
  placeholder: '输入内容...',
  MENU_CONF: {
    uploadImage: { // 上传图片的配置
      uploadImgServer: '/api/upload-image',
      // 自定义图片上传
      async customUpload(file: File, insertFn: any) {
        if (file.type !== 'image/jpeg' && file.type !== 'image/png') {
          ElMessage.info('图片必须是jpg或png格式!')
          return
        }
        if (file.size > 1024 * 1024 * 2) {
          ElMessage.info('图片大小不能超过2M!')
          return
        }
        // 上传图片
        uploadFileApi(file, '').then((res: any) => {
          // 上传成功后，获取图片地址，并插入到编辑器中
          if (res.data.code === 200) {
            const key = useCookies().cookies.get('username').split('_')[0]
            insertFn(downloadFileUrl + res.data.data + '&' + 'key=' + key + '&' + 'contentType=image/png', '无法显示')
          }
        })
      }
    },
    uploadVideo: {
      server: '/api/upload',
      // 自定义视频上传
      async customUpload(file: File, insertFn: any) {
        if (file.type !== 'video/mp4') {
          ElMessage.info('视频必须是mp4格式!')
          return
        }
        if (file.size > 1024 * 1024 * 200) {
          ElMessage.info('视频大小不能超过200M!')
          return
        }
        uploadFileApi(file, '').then((res: any) => {
          if (res.data.code === 200) {
            const key = useCookies().cookies.get('username').split('_')[0]
            insertFn(downloadFileUrl + res.data.data + '&' + 'key=' + key + '&' + 'contentType=video/mp4')
          }
        })
      }
    },
    insertImage: {
      onInsertedImage(imageNode: any | null) {
        if (imageNode == null) return
        const { src } = imageNode
        insertImage.value.push(src)
      }
    },
    insertVideo: {
      onInsertedVideo(videoNode: any | null) {
        if (videoNode == null) return
        const { src } = videoNode
        insertVideo.value.push(src)
      }
    }
  }
}

// 组件销毁时，也及时销毁编辑器
onBeforeUnmount(() => {
  const editor = editorRef.value
  if (editor == null) return
  editor.destroy()
})

// 编辑器创建完毕的回调，记录 editor 实例，重要！
const handleCreated = (editor: any) => {
  editorRef.value = editor
}

// 保存
const save = (editor: any) => {
  if (valueHtml.value.length > props.size) {
    ElMessageBox.alert('内容超出可存储范围，请删减内容!', '警告', {
      confirmButtonText: 'OK'
    })
  } else {
    // ===============处理多余图片==============
    // 获取编辑器中存在的图片
    const imageTag = editor.getElemsByType('image')
    // 获取删除的图片
    let deleteImageList: any[]
    if (imageTag.length > 0) {
      // 获取编辑器中不存在的图片
      const imageList = imageTag.map((item: any) => item.src)
      deleteImageList = insertImage.value.filter(item => !imageList.includes(item))
    } else {
      // 如果编辑器中没有图片则删除所有插入的图片
      deleteImageList = insertImage.value
      insertImage.value = []
    }
    // 删除图片
    if (deleteImageList.length > 0) {
      let deleteImageListStr = ''
      deleteImageList.forEach(item => {
        // 获取图片名
        deleteImageListStr += item.substring(item.indexOf('name=') + 5, item.indexOf('&')) + ','
      })
      deleteImageList.length = 0
      deleteFileApi(deleteImageListStr).then((res: any) => {
        if (res.data.code === 200) {
          ElMessage.success('删除图片成功')
        }
      })
    }
    // ===============处理多余视频==============
    const videoTag = editor.getElemsByType('video')
    let deleteVideoList: any[]
    if (videoTag.length > 0) {
      const videoList = videoTag.map((item: any) => item.src)
      deleteVideoList = insertVideo.value.filter(item => !videoList.includes(item))
    } else {
      deleteVideoList = insertVideo.value
      insertVideo.value = []
    }
    // 删除视频
    if (deleteVideoList.length > 0) {
      let deleteVideoListStr = ''
      deleteVideoList.forEach(item => {
        deleteVideoListStr += item.substring(item.indexOf('name=') + 5, item.indexOf('&')) + ','
      })
      deleteVideoList.length = 0
      deleteFileApi(deleteVideoListStr).then((res: any) => {
        if (res.data.code === 200) {
          ElMessage.success('删除视频成功')
        }
      })
    }
    emit('save', valueHtml.value)
  }
}

// 清空富文本内部资源
const clear = async () => {
  if (insertImage.value.length > 0) {
    let deleteImageListStr = ''
    insertImage.value.forEach(item => {
      deleteImageListStr += item.substring(item.indexOf('name=') + 5, item.indexOf('&')) + ','
    })
    await deleteFileApi(deleteImageListStr).then((res: any) => {
      if (res.data.code === 200) {
        ElMessage.success('删除图片成功')
      }
    })
  }
  if (insertVideo.value.length > 0) {
    let deleteVideoListStr = ''
    insertVideo.value.forEach(item => {
      deleteVideoListStr += item.substring(item.indexOf('name=') + 5, item.indexOf('&')) + ','
    })
    await deleteFileApi(deleteVideoListStr).then((res: any) => {
      if (res.data.code === 200) {
        ElMessage.success('删除视频成功')
      }
    })
  }
}

onMounted(() => {
  if (props.enableImg && props.enableVideo) {
    // 默认屏蔽工具栏上的图片、视频按钮
    toolbarConfig.value = {
      excludeKeys: []
    }
    return
  }
  if (props.enableImg) {
    // 监听图片上传
    toolbarConfig.value = {
      excludeKeys: ['insertVideo']
    }
  }
  if (props.enableVideo) {
    // 监听视频上传
    toolbarConfig.value = {
      excludeKeys: ['group-image']
    }
  }
})

defineExpose({
  clear
})
</script>

<template>
  <div style="border: 1px solid #ccc;z-index: 1000">
    <Toolbar
      style="border-bottom: 1px solid #ccc"
      :editor="editorRef"
      :defaultConfig="toolbarConfig"
      mode="default"
    />
    <Editor
      style="height: 400px; overflow-y: hidden;"
      v-model="valueHtml"
      :defaultConfig="editorConfig"
      mode="default"
      @onCreated="handleCreated"
      @onChange="save"
    />
  </div>
</template>

<style scoped>

</style>
